


// For production use.
// http://karlagius.wordpress.com
// TODO: Need to figure out how arguments.caller works and where arguments.callee has gone. Both items would be useful to have.
// TODO: Need to find out how to derive the name of the target function if at all possible.

// Performs a deep copy of an object. This allows us to store the
// original function when we apply an aspect to it.
// ref http://www.irt.org/script/879.htm
//function CloneObject(instance) {
//	for (i in instance) {
//		if (typeof instance[i] == 'object') {
//			this[i] = new CloneObject(instance[i]);
//		}
//		else
//			this[i] = instance[i];
//	}
//}

// Utility method, applies a target and aspect to the implementation specifying the aspect's order.
//function Aspect(target, aspect, implementation)
//{
//	implementation["target"] = CloneObject(target);
//	implementation["aspect"] = aspect;
//	
//	return implementation;
//}

// Applies an aspect to the target function, which will be executed after the function has exited successfully.
// The aspect will be provided with the arguments to the function, and the return value.
// For the purpose of this function, a successful exit will be considered to be any return not throwing an exception.
//function AspectOnSuccess(target, aspect)
//{
//
//	return Aspect(target, aspect, function() {
//			var val = target.apply(arguments.caller, arguments);
//			aspect(arguments, val);
//			return val;
//		});
//
//	
//}

// Applies an aspect to the target function, which will be run before the target is executed.
// The aspect will be provided with the arguments to a function.
//function AspectBefore(target, aspect)
//{
//	return Aspect(target, aspect, function() {
//			aspect(arguments);
//			var val = target.apply(target, arguments);
//			return val;
//		});
//}



//for (i in Prototype.Browser) {
//    Prototype.Browser[i.toLowerCase()] = Prototype.Browser[i];
//}

Function.prototype.deprecate = function(msg){
    var that = this;
    
    //return that;
    
    return function(){
        console.log(msg);
        return that.apply(this, arguments);
    }
}

function aspectOnHtmlMethod(meth){
	return function(){
			return meth.apply(null, [this.element].concat($A(arguments)));
	}
}

function aspectOnMootoolsElement(meth){
	return function(){
			return meth.apply(this.element, arguments);
	}
}

//Function.prototype.htmlize = function(){
//	var me = this;
//	return function(){
//			return me.apply(null, [this.element].concat($A(arguments)));
//	}
//}
//Runs a function-filter before running target. filter must return true for target to run.
AspectBefore = function(target, filter){
    return function(){
        var args = $A(arguments)
        if (filter.apply(this, args)) 
            return target.apply(this, args)
    }
}


// Applies an aspect to the target function, which will be run if an exception is raised by the target.
function AspectOnException(target, aspect, scope){
    var val = null;
    return function(){
        try {
            val = target.apply(scope, arguments);
        } 
        catch (err) {
            var ran = false;
            if (typeof aspect == "function") {
                val = aspect.apply(scope, [err]);
                ran = true;
            }
            else 
                if (typeof aspect == "object") {
                    //console.log(err.name == "Error")
                    /*if (aspect["Error"]) {
                 val = aspect["Error"].apply(scope, [err]); ran=true;
                 }*/
                    if (aspect[err.name]) {
                        val = aspect[err.name].apply(scope, [err]);
                        ran = true;
                    }
                    else {
                        if (aspect["Error"]) {
                            val = aspect["Error"].apply(scope, [err]);
                            ran = true;
                        }
                        else {
                            val = err;
                            throw new Error("No specific macro found to handle exception of: " + err)
                        }
                        
                        
                    }
                }
                else {
                    val = err;
                }
            return val
        }
        return val;
    };
    //	return Aspect(target, aspect, function() {
    //			var val;
    //
    //			try
    //			{
    //				val = target.apply(arguments.caller, arguments);
    //
    //			}
    //			catch(err)
    //			{
    //				aspect(arguments, err);
    //			}
    //
    //			return val;
    //		});
}


function AspectOnAction(target, scope){

}











Aspects = {};
Aspects.registry = {};
Aspects.add = function(aspect){
    Aspects.registry[aspect.keyword] = aspect;
}
Aspects.apply = function(obj){
    var klone = obj;
    for (i in Aspects.registry) 
        Aspects.registry[i].apply(klone);
}


Aspect_Will_Create_Friendly_Aliases_For_Any_Method_Modifiers = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var _methodName = RegExp.$1;
                var f = clone[member];
                //				console.log("alias for " + member + " to --> " + _methodName)
                clone[_methodName] = f.bind();
                delete clone[member];
            }
        }
        return clone;
    },
    keyword: /^.*\s(.+)/
}

Aspect_Hides_Private_Methods = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var _methodName = RegExp.$1;
                var f = clone[member];
                //				console.log("alias for " + member + " to --> " + _methodName)
                clone["__" + _methodName + "__"] = f.bind();
                delete clone[member];
            }
        }
        return clone;
    },
    keyword: /^\-.*\s(.+)/
}

//wrap = function(f){
//	return function(){
//		return f.apply(this,arguments);
//	}
//}

Aspect_Deletes_NonSupported_Browser_Functions = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
				var name = RegExp.$1 || "";
                if(name && (name != Browser.Engine.name)) //(!Prototype.Browser[RegExp.$1]) 
                    delete clone[member];
            }
        }
        return clone;
    },
    keyword: /\b(mobilesafari|opera|ie|webkit|gecko)\b.*\s(.+)/
}

Aspect_Rescues_Any_Exceptions = {
    apply: function(clone){
        if (clone.rescue) {
            for (i in clone) {
                if (typeof clone[i] == "function") 
                    clone[i] = AspectOnException(clone[i], clone.rescue, clone);
            }
        }
        return clone;
    },
    keyword: "rescue"
}


Aspect_Creates_Static_Constructor_Methods = {
    apply: function(clone){
        for (var member in clone) 
            if (this.keyword.test(member)) {
                clone[RegExp.$2] = clone[member];
                delete clone[member];
            }
        return clone;
    },
    keyword: /\b(class)\b.*\s(.+)/
}

Function.prototype.getName = function(){
    var that = this;
    if (that.name) {
        return that.name;
    }
    
    // try to parse the function name from the defintion
    var definition = that.toString();
    var name = definition.substring(definition.indexOf('function') + 8, definition.indexOf('('));
    if (name) 
        return name;
    
    // sometimes there won't be a function name 
    // like for dynamic functions
    return "anonymous";
}

Function.prototype.stacktrace = function(){

    return function(){
        var that = this;
        var stackTraceMessage = "Stack trace: <br>\n";
        var nextCaller = arguments.callee;
        while (nextCaller) {
            stackTraceMessage += getSignature(nextCaller) + "<br>\n";
            nextCaller = nextCaller.caller;
        }
        stackTraceMessage += "<br>\n\n";
        
        // display message
        //	document.getElementById("output").innerHTML = stackTraceMessage + document.getElementById("output").innerHTML;
        console.log(stackTraceMessage)
        that.apply(this, arguments)
    }
}

function getFunctionName(theFunction){
    // mozilla makes it easy. I love mozilla.
    if (theFunction.name) {
        return theFunction.name;
    }
    
    // try to parse the function name from the defintion
    var definition = theFunction.toString();
    var name = definition.substring(definition.indexOf('function') + 8, definition.indexOf('('));
    if (name) 
        return name;
    
    // sometimes there won't be a function name 
    // like for dynamic functions
    return "anonymous";
}

function getSignature(theFunction){
    var signature = getFunctionName(theFunction);
    signature += "(";
    for (var x = 0; x < theFunction.arguments.length; x++) {
        // trim long arguments
        var nextArgument = theFunction.arguments[x];
        if (nextArgument.length > 30) 
            nextArgument = nextArgument.substring(0, 30) + "...";
        
        // apend the next argument to the signature
        signature += "'" + nextArgument + "'";
        
        // comma seperator
        if (x < theFunction.arguments.length - 1) 
            signature += ", ";
    }
    signature += ")";
    return signature;
}


//Profile = function(func){
//	return function(){
//		var start_time = (new Date()).getTime();
//		//                    console.log("Profiling method " + ((f.name) ? "named " + f.name : ""))
//		var res = func.apply(this, arguments);
//		var end_time = (new Date()).getTime();
//		console.log("\t-->Took " + (end_time - start_time) + " milliseconds(ms) to execute '" + f.name + "'")
//		return res;
//	}
//}
Function.prototype.add_filter = function(_func){
    var _self = this;
    var f = function f(){
        for (i = 0; i <= _self.filters.length - 1; i++) 
            if (_self.filters[i].apply(this, arguments)) 
                return _self.apply(this, arguments)
    }
    if (!_self.filters) 
        _self.filters = [];
    _self.filters.push(_func);
    return f;
}

//Function.prototype.prepareFilters = function(){
//	var that = this;
//	return function(){
//		if (that.beforeFilters) {
//			for (i=0; i<=that.beforeFilters.lenght-1; i++) {
//				//that.beforeFilters[i].apply(this, arguments);
//				console.log("count: " + i)
//			}
//		}
//	}
////	if (that.beforeFilters) {
////		return function(){
////			for (i=0; i<=that.beforeFilters.lenght; i++) {
////				that.beforeFilters[i].apply(this, arguments);
////			}
////			console.log("finish running all filters. Executing target fuction now..");
////			return that.apply(this, arguments);
////		}
////	}
////	else {
////		return that;
////	}
//	console.log("filters array: " + that.beforeFilters)
//}


Function.prototype.deprecation = function(deprecatedName, newName){
    //console.log("DEPRECATED:")
    var that = this;
    return function(){
        console.log("DEPRECATION WARNING: '" + deprecatedName + "' is deprecated for '" + newName + "'");
        return that.apply(this, arguments);
    }
}
Function.prototype.cache = function(){
    var that = this;
    return function(){
        if (that.some) {
            console.log("*from cache:" + that.some);
            return that.some;
        }
        else {
            //that.args = arguments;
            var res = that.apply(this, arguments);
            that.some = res;
            
            return that.some;
        }
    }
}

Function.prototype.profile = function(){
    var that = this;
    return function(){
        var start_time = (new Date()).getTime();
        var res = that.apply(this, arguments);
        var end_time = (new Date()).getTime();
        console.log("\t--> " + ((end_time - start_time)/1000) + " (ms)");
        return res;
    }
}


Aspect_On_Deprecation_Warnings = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var f = clone[member];
                clone[member] = f.deprecation(member.split(" ").pop(), RegExp.$1);
            }
        }
        return clone;
    },
    keyword: /!([a-z]+)/
}


Aspect_Watches_Method_Timings = {
    apply: function(clone){
		
        for (var member in clone) {
            if (this.keyword.test(member)) {
				//console.log("applying aspect: Aspect_Watches_Method_Timings")
                var f = clone[member];
                clone[member] = f.profile();
				//console.log(f.toString())
            }
        }
        return clone;
    },
    keyword: /\b(profile)\b.*\s(.+)/
}


Aspect_On_Stacktraces = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var f = clone[member];
                clone[member] = f.stacktrace();
            }
        }
        return clone;
    },
    keyword: /\b(stacktrace)\b.*\s(.+)/
}


Function.prototype.before = function(_filter){
    var that = this;
    var filter = _filter;
    return function(){
        //console.log("before filter")
        filter.apply(this, arguments);
        return that.apply(this, arguments)
    }
}
Aspect_On_Before_Filters = {
    apply: function(clone){
        if (clone.before) {
            for (i in clone.before) {
                if (clone[i] && typeof clone[i] == "function") {
                    var filter = clone.before[i];
                    var f = clone[i];
                    clone[i] = f.add_filter(filter); //AspectBefore(func, filter);
                }
            }
            delete clone.before;
        }
        return clone;
    }
}


//http://en.wikipedia.org/wiki/Memoization
//Caches the return value and prevents the function from running again.
//optimization technique.
Aspect_On_Memoized_Functions = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var f = clone[member];
                clone[member] = f.cache();
            }
        }
        return clone;
    },
    keyword: /\b(memorize|memoize|cache)\b.*\s(.+)/
}

Function.prototype.undoable = function(){
	var that = this;
    return function(){
        var undofunc = that.apply(this, arguments);
        if(!this.undos) this.undos = [];
		this.undos.push(undofunc.undo);
		return undofunc;
    }
}

Aspect_On_Undoable_Functions = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
                var f = clone[member];
                clone[member] = f.undoable();
            }
        }
        return clone;
    },
    keyword: /\b(undoable)\b.*\s(.+)/
}

Function.prototype.action = function(http_verb){
	var that = this;
    return function(){
        var funcResult = that.apply(this, arguments);
		//console.log(this.classname)
		//console.log("undo function is: " +  funcResult.undo)
		//console.log(":controller => " + clone.classname.toLowerCase().pluralize() + " :action => " + that.funcName)
//        new Ajax.Request("/" + this.classname.toLowerCase().pluralize() + "/" + that.funcName, {
		if(http_verb == "get") {
			new Ajax.Request("/" + this.classname.toLowerCase().pluralize(), {
				method : http_verb,
				onSuccess : function(r){console.log(r.responseText)},
				onFailure : function(r){console.log("failure:\n" + r.responseText)}
			});
		}
		if(http_verb == "post") {
			new Ajax.Request("/" + this.classname.toLowerCase().pluralize(), {
				method : http_verb,
				postBody	: Object.toJSON(funcResult),
				contentType	: "application/json",
				onSuccess : function(r){console.log("success:\n" + r.responseText)},
				onFailure : function(){console.log("failure:\n" + r.responseText)}
			});
		}
		if(http_verb == "delete") {
			new Ajax.Request("/" + this.classname.toLowerCase().pluralize() + "/" + funcResult.deleted.id.split("_")[1], {
				method : http_verb,
				contentType: "application/x-www-form-urlencoded",
				onSuccess : function(r){console.log("success:\n" + r.responseText)},
				onFailure : funcResult.undo
			});
		}
    }
}
Aspect_On_Rails_Actions = {
    apply: function(clone){
        for (var member in clone) {
            if (this.keyword.test(member)) {
				var http_verb = RegExp.$1;
                var f = clone[member];
					f.funcName = RegExp.$2.replace(/(.+\:)/,"");
                clone[member] = f.action(http_verb);
            }
        }
        return clone;
    },
    keyword: /\b(get|post|put|delete)\b.*\s(.+)/
}



/////
Aspects.add(Aspect_Deletes_NonSupported_Browser_Functions);
//Aspects.add(Aspect_Creates_Static_Constructor_Methods);
Aspects.add(Aspect_Watches_Method_Timings);
Aspects.add(Aspect_On_Memoized_Functions);
Aspects.add(Aspect_On_Deprecation_Warnings);
Aspects.add(Aspect_On_Stacktraces);
Aspects.add(Aspect_On_Undoable_Functions); //must come before Aspect_On_Rails_Actions
Aspects.add(Aspect_On_Rails_Actions);
//
//
//Aspects.add(Aspect_Hides_Private_Methods);
Aspects.add(Aspect_Will_Create_Friendly_Aliases_For_Any_Method_Modifiers);
Aspects.add(Aspect_On_Before_Filters);
//Aspects.add(Aspect_Rescues_Any_Exceptions);




